home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (C) 1993 Robert Davis
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of Version 2, or any later version, of
- * the GNU General Public License as published by the Free Software
- * Foundation.
- */
-
- static char RCSId[]="$Id: Gnuplot.m,v 1.12 1993/05/30 09:10:13 davis Exp $";
-
-
- #import <appkit/Application.h>
- #import <appkit/Listener.h>
- #import <appkit/Matrix.h>
- #import <appkit/Menu.h>
- #import <appkit/MenuCell.h>
- #import <appkit/OpenPanel.h>
- #import <appkit/SavePanel.h>
- #import <appkit/PrintInfo.h>
- #import <appkit/Speaker.h>
-
- #import <appkit/publicWraps.h> /* NXConvertWinNumToGlobal() */
-
- #import <objc/List.h>
- #import <objc/NXStringTable.h>
-
- #import <libc.h> /* MAXPATHLEN */
- #import <math.h> /* M_PI */
- #import <strings.h> /* strcmp() */
-
- #import "Gnuplot.h"
- #import "GnuplotPlot.h"
- #import "Inspector.h"
- #import "Preferences.h"
- #import "Version.h"
-
- #define MAX_ID_LEN 50
-
- extern struct value { /* structs from plot.h */
-
- enum DATA_TYPES {INTGR, CMPLX} type;
- union {
- int int_val;
- struct cmplx {
- double real, imag;
- } cmplx_val;
- } v;
-
- } *Gcomplex();
-
- extern struct udvt_entry {
-
- struct udvt_entry *next_udv;
- char udv_name[MAX_ID_LEN+1];
- int udv_undef;
- struct value udv_value;
-
- } *first_udv;
-
-
-
- /*
- * This function is from the NeXTSTEP Developer Example Draw by Paul
- * Hegarty.
- *
- * Sets the updateAction for every menu item that sends its action to
- * the First Responder (i.e. every menu item whose target is nil).
- * When autoupdate is on (which is always in this app), each event
- * will be followed by an update of every visible menu item. This
- * keeps all unavailable menu items dimmed and disabled so that the
- * user knows what options are available at any given time.
- */
- static void initMenu (Menu* menu)
- {
- int count;
- Matrix *matrix;
- MenuCell *cell;
- id matrixTarget, cellTarget;
-
- matrix = [menu itemList];
- matrixTarget = [matrix target];
-
- count = [matrix cellCount];
- while (count--) {
-
- cell = [matrix cellAt:count :0];
- cellTarget = [cell target];
-
- if (!matrixTarget && !cellTarget)
- [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
- else if ([cell hasSubmenu])
- initMenu(cellTarget);
-
- }
- }
-
-
- @implementation Gnuplot
-
- + setConstantUpdate:(int)updateType
- {
- return [GnuplotPlot setConstantUpdate:updateType];
- }
-
-
- + setHalvePlot:(BOOL)condition
- {
- return [GnuplotPlot setHalvePlot:condition];
- }
-
-
- - init
- {
- [super init];
-
- zone = [self zone];
-
- docList = [[List allocFromZone: zone] init];
- numUntitled = 1;
- isPoweringOff = NO;
-
- /* Set printinfo margins to 1/2 inch (36 points) on each side */
- [[NXApp printInfo] setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
-
- [NXApp setAutoupdate:YES];
-
- preferences = [[Preferences allocFromZone: zone] init];
- inspector = [[Inspector allocFromZone: zone] init];
-
- /* Setup the first user-defined variable to be pi */
- Gcomplex(&(first_udv->udv_value), M_PI, 0.0);
-
- return self;
- }
-
-
- // This is never called
- - free
- {
- [infoPanel free];
- [copyingPanel free];
- [inspector free];
- [preferences free];
- [stringSet free];
- [docList freeObjects];
- [docList free];
-
- return [super free];
- }
-
-
- - stringSet
- {
- return stringSet;
- }
-
-
- - showInfoPanel:sender
- {
- if (!infoPanel) {
- extern char version[];
- char rev[255];
-
- [NXApp loadNibSection: "InfoPanel.nib"
- owner: self
- withNames: NO
- fromZone: zone];
-
- /*
- * nextstep_version is a string constant defined and
- * initialized in Version.h that is automatically update by
- * RCS. It will contain "$Revision: " followed by the
- * revision number. The last character will be a '$'.
- * version is a string defined in version.c and is only a
- * number.
- */
-
- strcpy (rev, nextstep_version);
- rev[strlen(rev) - 1] = '\0'; /* Remove last $ */
- [nextstepVersionTextField setStringValue:&(rev[11])];
- [versionTextField setStringValue:version];
- }
-
- [infoPanel makeKeyAndOrderFront:self];
- return self;
- }
-
-
-
- - showCopyingPanel:sender
- {
- if (!copyingPanel)
- [NXApp loadNibSection: "CopyingPanel.nib"
- owner: self
- withNames: NO
- fromZone: zone];
-
- [copyingPanel makeKeyAndOrderFront:self];
-
- return self;
- }
-
-
-
- - showInspectorPane:sender
- {
- /* Inspector was created in -init */
-
- [inspector selectPane:[sender selectedTag]];
- [[inspector window] makeKeyAndOrderFront:self];
- return self;
- }
-
-
-
- - showPreferencesPanel:sender
- {
- /* Preferences was created in -init */
-
- [preferences showPanel:self];
- return self;
- }
-
-
-
- - new:sender
- {
- id newDoc = nil;
-
- if (newDoc = [[GnuplotPlot allocFromZone: zone] init]) {
- numUntitled++;
- [docList addObject:newDoc];
- }
-
- return newDoc;
- }
-
-
-
- - open:sender
- {
- const char *const *files;
- static const char *const fileType[2] = {DOCUMENT_TYPE, NULL};
- OpenPanel *openPanel;
- id newDoc;
- char fullName[MAXPATHLEN];
-
- openPanel = [[OpenPanel new] allowMultipleFiles:YES];
- [openPanel setTitle:[stringSet valueForStringKey:"openT"]];
-
- /* Get a list of files to open from the user. */
- if ([openPanel runModalForTypes:fileType]) {
-
- /* For each pathname selected... */
- for (files = [openPanel filenames]; files && *files; files++) {
-
- sprintf (fullName, "%s/%s", [openPanel directory], *files);
-
- /*
- * ... check to see if the doc is already opened. If it
- * is, bring it to the front.
- */
- if (newDoc = [self isDocOpen: fullName])
- [[newDoc window] makeKeyAndOrderFront:self];
-
- /* If not, try to create a new one. */
- else if ((newDoc = [[GnuplotPlot allocFromZone: zone]
- initFromFile: fullName]))
- [docList addObject: newDoc];
-
-
- /*
- * Otherwise, just return. GnuplotPlot will report the
- * nature of the error to the user so we don't need to
- * here.
- */
- else
- return nil;
-
- }
-
- }
-
- return self;
- }
-
-
-
- - saveAll:sender
- {
- [docList makeObjectsPerform:@selector(save:) with:self];
- return self;
- }
-
-
- - setSaveType:sender
- {
- id savePanel = [SavePanel new];
-
- switch ([sender selectedTag]) {
- case SAVE_GNUPLOT:
- [savePanel setRequiredFileType:DOCUMENT_TYPE]; break;
- case SAVE_EPS:
- [savePanel setRequiredFileType:"eps"]; break;
- case SAVE_TIFF:
- [savePanel setRequiredFileType:"tiff"]; break;
- }
-
- return self;
- }
-
-
- - (int)saveType
- {
- return [savePanelTypeMatrix selectedTag];
- }
-
-
- /*
- * This method should update any panels external to the doc which
- * reflect the current doc (e.g. the Inspector panel).
- */
- - updateApp
- {
- [inspector update];
- return self;
- }
-
-
-
-
- /*
- * The doc should send this when it closes so that we can remove it
- * from our doc list.
- */
- - docDidClose:sender
- {
- [docList removeObject:sender];
- [self setCurrentDoc:nil];
- return self;
- }
-
-
-
- /*
- * Checks the doc list to see if the doc specified by fullPath is
- * currently open. If it is, the doc is returned. Otherwise, nil is
- * returned.
- */
- - isDocOpen: (const char *) fullPath
- {
- int counter, docCount = [docList count];
- id aDoc;
-
- for (counter = 0 ; counter < docCount ; counter++)
- if (!strcmp ([aDoc = [docList objectAt: counter] name], fullPath))
- return aDoc;
-
- return nil;
- }
-
-
-
-
- - (int)numberNew
- {
- return numUntitled;
- }
-
-
-
- - setCurrentDoc:(GnuplotPlot *)aDoc
- {
- if (aDoc)
- currentDoc = aDoc;
- else
- currentDoc = [[NXApp mainWindow] delegate];
-
- return self;
- }
-
-
- - (GnuplotPlot *)currentDoc
- {
- return currentDoc;
- }
-
-
-
-
- /*** Auto-Update Methods ***/
-
-
-
- /*
- * This method is from the NeXTSTEP example Draw by Paul Hegarty.
- *
- * When a menu is updated, this method is sent by each menu item that
- * sends its action to the First Responder. If the object that would
- * respond to an action sent down the Responder chain also responds
- * to the validateCommand:, then we send validateCommand: to
- * determine whether the menu action is valid now. Otherwise, if
- * there is a Responder to the message, then we assume that the item
- * is valid. The method returns YES if the cell has changed its
- * appearance (so that the caller Menu knows to redraw it).
- */
- - (BOOL)menuItemUpdate:menuCell
- {
- SEL action;
- id responder, target;
- BOOL enable;
-
- target = [menuCell target];
- enable = [menuCell isEnabled];
-
- if (!target) {
- action = [menuCell action];
- responder = [NXApp calcTargetForAction:action];
- if ([responder respondsTo:@selector(validateCommand:)])
- enable = [responder validateCommand:menuCell];
- else
- enable = responder ? YES : NO;
-
- }
-
- if ([menuCell isEnabled] != enable) {
- [menuCell setEnabled:enable];
- return YES;
- } else
- return NO;
-
- }
-
-
- /*
- * This method is sent down the Responder chain by menuItemUpdate:
- *
- * Every object that responds to messages sent down the Responder
- * chain by menu cells but occasionally can't do perform the action
- * should implement validateCommand: and return a boolean value
- * telling whether the menu item should be enabled or not.
- *
- * For example: we can respond to saveAll:, but we can't save all if
- * there are no docs that need saving. So we need to implement
- * validateCommand: to disable the saveAll: menu cell when it is
- * irrelevant.
- */
- - (BOOL)validateCommand:menuCell
- {
- SEL action = [menuCell action];
- int count;
-
- if (action == @selector(saveAll:)) {
-
- if ([docList count])
- for (count = [docList count] - 1; count >= 0 ; count--)
- if ([[docList objectAt:count] validateCommand:menuCell])
- return YES;
-
- return NO;
-
- }
-
- return YES;
- }
-
-
-
-
-
-
-
- /*** Application Delegate Methods ***/
-
-
- /* Sent when user opens one of our docs from the Workspace. */
-
- - (int)app:sender openFile:(const char *)path type:(const char *)type
- {
- id newObject;
-
- /* If the document is of the right type... */
- if (type && !strcmp (type, DOCUMENT_TYPE)) {
-
- /*
- * ... first check to see if it's already open. If it is,
- * bring it to the front.
- */
- if (newObject = [self isDocOpen: path])
- [[newObject window] makeKeyAndOrderFront:self];
-
-
- /* Otherwise, attempt to open the document. */
- else if (newObject = [[GnuplotPlot allocFromZone: zone]
- initFromFile:path]) {
-
- [docList addObject:newObject];
-
- /*
- * Set the OpenPanel directory so that when the user opens
- * another doc, the open panel will start where he/she left
- * off.
- */
- [[OpenPanel new] setDirectory:path];
-
- /*
- * This causes documents opened from the Workspace to
- * open more slowly, but if we don't the Inspector will
- * be outdated until the next event.
- */
- [self updateApp];
- return YES;
-
- }
-
- }
-
- return NO;
- }
-
-
- - app:sender powerOffIn:(int)ms andSave:(int)aFlag
- {
- isPoweringOff = YES;
- return [self appWillTerminate:self];
- }
-
-
-
- - (BOOL)appAcceptsAnotherFile:sender
- {
- /*
- * We return YES to this because we can open any number of
- * documents.
- */
- return YES;
- }
-
-
- - appDidInit:sender
- {
- id savePanel = [SavePanel new];
- int inspectorPref;
-
- initMenu([NXApp mainMenu]); /* Setup auto-menu-update */
-
- /*
- * Set services delegate to be self so this app can be a
- * service-provider and this object can handle the messages.
- */
- [[NXApp appListener] setServicesDelegate:self];
-
-
- /* Set save panel's accessory view */
- [savePanel setAccessoryView: [savePanelAccessory contentView]];
- [self setSaveType:savePanelTypeMatrix];
-
- /*
- * Open a new, untitled document, unless the app was started when
- * the user opened a document from the Workspace or the user has
- * specified not to with Preferences.
- */
- if (![docList count] && [preferences newDocument])
- [self setCurrentDoc:[self new:self]];
-
- /*
- * We make the inspector the key window down here, last, so that
- * it will be key when the user begins using the program. Select
- * user's preferred pane, too.
- */
- inspectorPref = [preferences defaultInspector];
- if (inspectorPref != NO_INSPECTOR) {
- [inspector selectPane: inspectorPref];
- [[inspector window] makeKeyAndOrderFront:self];
- }
-
- return self;
- }
-
-
-
- - appWillTerminate: sender
- {
- int editedCount, counter;
- id editedDocs = [[List allocFromZone: zone] init];
- id aDoc;
-
- do {
-
- /* Build a list of edited, unsaved docs. */
-
- [editedDocs empty];
- counter = [docList count];
- while (--counter >= 0) {
- aDoc = [docList objectAt: counter];
- if ([aDoc isDocEdited])
- [editedDocs addObject: aDoc];
- }
-
- /*
- * If there are edited, unsaved docs, ask if the user wants to
- * review them, quit, or cancel the quit action (unless this
- * method was sent by us during a poweroff/logoff, in which
- * case we don't allow a cancel).
- */
- if ((editedCount = [editedDocs count]) > 0) {
- switch (NXRunAlertPanel ([stringSet valueForStringKey: "quitT"],
- [stringSet valueForStringKey: "unsavedDocs"],
- [stringSet valueForStringKey: "reviewB"],
- [stringSet valueForStringKey: "quitB"],
- isPoweringOff? NULL: [stringSet valueForStringKey: "cancelB"])) {
-
- case NX_ALERTDEFAULT: /* Review Unsaved */
-
- counter = editedCount;
- while (--counter >= 0)
- [[editedDocs objectAt: counter] close:self];
- break;
-
- case NX_ALERTALTERNATE: /* Quit Anyway */
- editedCount = 0;
- break;
-
- case NX_ALERTOTHER: /* Cancel */
- [editedDocs free];
- return nil;
- break;
- }
- }
-
- /*
- * If the user chooses to review unsaved, then is shown a Save
- * panel for a doc and chooses "Cancel," we end up down here,
- * where we'll repeat if there are more unedited docs.
- * Basically, we force the user either to save all the docs or
- * make a decision in the quit panel to review, quit, or
- * cancel.
- */
-
- } while (editedCount > 0);
-
- [editedDocs free];
-
-
- /*
- * Quit the application. (We free things here because -free is
- * never called -- NXApp simply exit()'s -- but GnuplotPlot's
- * implementation of -free deletes the plot's tmp file, so we
- * must call it to avoid leaving junk in /tmp.)
- */
-
- [docList makeObjectsPerform: @selector(free)];
- [docList free];
-
- return self;
- }
-
-
-
- /*
- * This method is sent when a user chooses one of Gnuplot's services
- * from the Services menu in another application.
- */
- - plotData:pb userData:(const char *)userData error:(char **)errorMessage
- {
- char *data;
- int length, i;
- const char *const *types;
- BOOL hasFilename;
-
- types = [pb types];
- hasFilename = NO;
- for (i=0 ; !hasFilename && types[i] ; i++)
- if (!strcmp (types[i], NXFilenamePboardType)) hasFilename = YES;
-
- if (hasFilename) {
- [pb readType:NXFilenamePboardType data:&data length:&length];
-
- if (data && length) {
- BOOL useCurrent = (currentDoc && strcmp (userData, "new"));
- char *path = NXCopyStringBufferFromZone (data, zone);
- char *beg, *end;
- BOOL atEnd;
-
- /*
- * If there is a current document, we add each data file
- * specified on the pasteboard to it. If there's no
- * current document, or if the userData contains "new"
- * we create a new document and add the data file to
- * that.
- */
-
- beg = path;
- atEnd = beg? NO:YES;
- while (!atEnd) {
- end = index (beg, '\t');
-
- if (end && *end && *end == '\t')
- *end = '\0';
- else
- atEnd = YES;
-
- if (useCurrent)
- [currentDoc addDataFile:beg];
- else {
- id newDoc = [self new:self];
- [newDoc addDataFile:beg];
- [self setCurrentDoc:newDoc];
- useCurrent = YES; /* Just create one new doc for all */
- }
-
- if (!atEnd) {
- *end = '\t';
- beg = end + 1;
- }
- }
-
- NXZoneFree(zone, path);
- [self updateApp];
- }
- }
-
- return self;
- }
-
-
- // Shuts up the compiler about unused RCSId
- - (const char *) rcsid
- {
- return RCSId;
- }
-
-
- @end
-
-